﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Noris.Tools.TraceVisualiser.Support;
using System.Windows.Forms;
using System.Drawing;

namespace Noris.Tools.TraceVisualiser.Components
{
    #region CLASS GridGraphCls : ČÁST HLAVNÍ TŘÍDY, KTERÁ ŘÍDÍ SELECTIONS A REDRAW PŘI FRAMOVÁNÍ
    /// <summary>
    /// Třída pro zobrazování grafu některého typu v rámci gridu
    /// </summary>
    // ŘEŠENÉ OBLASTI : SELECT ELEMENT
    internal partial class GridGraphCls : DblGraphControl, ILeaveFocus
    {
        /// <summary>
        /// Obsahuje (vždy nově generuje) seznam všech aktuálně vybraných elementů.
        /// </summary>
        internal IEnumerable<IDataElement> SelectedElements
        {
            get
            {
                if (this._SelectedInfo == null) return new List<IDataElement>();
                List<DataPointerStr> selectedPointers = this._SelectPermanentStateGet();
                return this._GridData.GetElementsIData(selectedPointers, true);
            }
        }
        /// <summary>
        /// Iniciace subsystému Selections
        /// </summary>
        private void _SelectInit()
        {
            this._SelectedInfo = new GridGraphSelectCls();
            this._SelectedInfo.FindElementMethod = this._GridData.GetElementData;
        }
        /// <summary>
        /// Instance subsystému Selections, řízení a evidence vybraných objektů
        /// </summary>
        private GridGraphSelectCls _SelectedInfo;
        /// <summary>
        /// Iniciační metoda pro Framing, volá se při začátku, kdy ještě není nic vybráno, zarámováno a ani vykresleno.
        /// Připraví si prostor pro soupisy řádků, které se v průběhu framování budou překreslovat.
        /// </summary>
        private void _SelectFramedBegin()
        {
            this._SelectedInfo.FrameRowClear();
        }
        /// <summary>
        /// Hlavní metoda pro přidání zarámovaných grafických elementů do selectoru.
        /// Metoda využívá souřadnice aktuálního frame (this._MouseFrameSelectedArea), z nichž si určí RowIndexy pomocí vyhledávací metody
        /// Tato metoda zajistí přidání elementů a překreslení dotčených řádků.
        /// </summary>
        private void _SelectFramedAddElementAndReDraw()
        {
            // Rozsah frame omezím jen na viditelnou plochu (průsečík Frame a plochy grafu):
            Rectangle framedArea = Rectangle.Intersect(this._MouseFrameSelectedArea, this._GraphArea);

            // Určíme rozsah řádků, kterých se týká FrameSelectem:
            int rowIndexFirst, rowIndexLast;
            this._DetectFramedRowRange(framedArea, GridGraphPaletteType.FrameSelection, out rowIndexFirst, out rowIndexLast);

            // Do selectoru přidáme rozsah nyní framovaných řádků (přidají se k již registrovaným z dřívějška, více viz tam):
            this._SelectedInfo.FrameRowAddRange(rowIndexFirst, rowIndexLast);

            // Původně jsem procházel vykreslené elementy a hledal jsem takové, jejichž plocha se alespoň zčásti shodovala s plochou frame.
            // Jenže tenhle postup vynechává ty elementy, jejichž šířka je příliš malá na to, aby byl element zobrazen.
            // Z hlediska zobrazení je to snad OK, ale pokud následně nad selectovanými elementy budu spouštět funkci, 
            // pak chci do funkce zahrnout i ty malé, prostým okem neviditelné elementy, které ale do framovaného času spadají.

            // Určím časový rozsah selectování (posuneme souřadnice Frame na ose X o počátek grafu this._GraphColumn.InnerLeft):
            int timeAxisLeft = this._GraphColumn.InnerLeft;
            TimeRange time = new TimeRange(this.TimeAxisCalc.GetTimeForVisual(framedArea.X - timeAxisLeft), this.TimeAxisCalc.GetTimeForVisual(framedArea.Right - timeAxisLeft));

            // Projdu řádky, které odpovídají framu:
            float selectCross = 0.333F;                   // Povinné překrytí elementu pro to, aby element spadl do výběru
            this._SelectedInfo.FrameClear();

			foreach (GridVisibleRowDataCls visRowData in this._VisibleRowDataList)
			{	// V kolekci this._VisibleRowDataList jsou jen řádky, které jsou nyní skutečně zobrazeny (od prvního částečně viditelného řádku po poslední),
				// kolekce nezahrnuje řádky mimo viditelnou oblast Controlu:
				if (visRowData.RowArea.IntersectsWith(framedArea))
                {   // Pokud viditelný řádek (jeho prostor) má něco společného s aktuálním SelectFramem, bude nás řádek zajímat:
                    // Určím Y fragment aktuálního řádku × SelectFrame, v koordinátech Controlu:
                    int y = (visRowData.RowArea.Y > framedArea.Y ? visRowData.RowArea.Y : framedArea.Y);   // Y = horní okraj: to větší (co je níže) z řádku / frame
                    int b = (visRowData.RowArea.Bottom < framedArea.Bottom ? visRowData.RowArea.Bottom : framedArea.Bottom);   // Bottom = dolní okraj: to menší (co je výše) z řádku / frame
                    OneDimensionI selectY = new OneDimensionI(y - visRowData.RowArea.Y, b - y);            // Prostor Y odpovídající SelectFrame v koordinátech řádku (Y == 0 => horní okraj řádku)

                    GridDataVisualLineCls dataLine = this._GridData.Rows[visRowData.RowIndex];
                    if (dataLine != null)
                    {
                        List<GridDataElementCls> selectedElements = dataLine.DataRow.FindElementsByFrame(time, selectY, selectCross);
                        this._SelectedInfo.FrameAddRange(selectedElements, dataLine.RowId);
                    }
                }
            }

            // 4. Vykreslíme řádky, kterých se týká framování:
            this.DrawFramedRows();

            // 5. Ze selectoru vyřadíme řádky, které leží mimo rozsah nyní framovaných řádků:
            if (!this._MouseFrameSelectedArea.IsEmpty)
                this._SelectedInfo.FrameRowCropRange(rowIndexFirst, rowIndexLast);
            else
                // Pokud ale nynější Frame je Empty (tj. má nulovou výšku nebo šířku), pak vyřadíme všechny řádky = nynější frame se netýká ničeho:
                this._SelectedInfo.FrameRowClear();

			// 6. Aktualizujeme status bar:
			this._SelectedInfo.ShowSelectedInfo();
        }
        /// <summary>
        /// Určí, zda bude třeba vykreslit prvky, které byly dosud PermanentSelected,
        /// ale po výběru nového prvku při režimu (combineType == GridGraphSelectCombineType.AddSolitaire) budou deselectovány.
        /// V takovém případě tyto deselectované prvky jsou nyní stále vizualizovány jako Selected, 
        /// ale po novém výběru je třeba je znovu zobrazit jako Standard.
        /// Tato metoda reaguje na režim kombinace selectu (parametr) a současný počet PermanentSelectedCount,
        /// a pokud zjistí že bude třeba Redraw, pak pro něj připraví data a vrátí true. Pokud Redraw nebude zapotřebí, vrátí false.
        /// </summary>
        /// <param name="combineType">Režim kombinace</param>
        /// <returns>true = bude třeba provést ReDraw / false = ne</returns>
        private bool _SelectPrepareReDrawSelected(GridGraphSelectCombineType combineType)
        {
            // Testy, zda bude ReDraw zapotřebí:
            if (combineType != GridGraphSelectCombineType.AddSolitaire) return false;
            if (this._SelectedInfo.PermanentSelectedCount == 0) return false;

            // Příprava pro ReDraw:
            this._SelectedInfo.PreparePermanentRowIndex(this._GridData.GetRowIndex);
            return true;
        }
        /// <summary>
        /// Připraví seznam řádků, jejichž některý element je ve stavu PermanentSelected.
        /// Seznam řádků je po provedení přípravy uložen v this._SelectedInfo.PermanentSelectedRowList, 
        /// ale je offline = změní se výhradně dalším voláním této metody this._SelectPreparePermanentRowIndex().
        /// Před prvním voláním je this._SelectedInfo.PermanentSelectedRowList == null.
        /// </summary>
        private void _SelectPreparePermanentRowIndex()
        {
            this._SelectedInfo.PreparePermanentRowIndex(this._GridData.GetRowIndex);
        }
        /// <summary>
        /// Metoda vrátí soupis informací o elementech, které jsou nyní vybrány (PermanentSelected).
        /// Soupis lze uschovat, a později znovu aplikovat voláním metody _SelectPermanentStateSet().
        /// </summary>
        /// <returns>Soupis elementů, které jsou PermanentSelected</returns>
        private List<DataPointerStr> _SelectPermanentStateGet()
        {
            return this._SelectedInfo.PermanentGet();
        }
        /// <summary>
        /// Metoda převezme soupis informací o elementech, které mají být vybrány, a zajistí jejich výběr do PermanentSelected.
        /// Soupis je typicky generován párovou metodou _SelectPermanentStateGet().
        /// </summary>
        /// <param name="elementPairList">Soupis elementů, které mají být PermanentSelected</param>
        /// <param name="reDraw">Požadavek na ReDraw</param>
        private void _SelectPermanentStateSet(List<DataPointerStr> elementPairList, bool reDraw)
        {
            this._SelectedInfo.PermanentClear();
            this._SelectedInfo.PermanentAddList(elementPairList);
            if (reDraw)
                this.Draw();
        }
    }
    #endregion
    #region CLASS GridGraphSelectCls : DATOVÁ TŘÍDA PRO ŘÍZENÍ VÝBĚRU (SELECTIONS)
    /// <summary>
    /// Podpůrná třída, která v sobě uchovává informace o výběrech grafických elementů
    /// </summary>
    internal class GridGraphSelectCls
    {
        #region KONSTRUKCE
        /// <summary>
        /// Konstruktor
        /// </summary>
        internal GridGraphSelectCls()
        {
            this._PermanentSelected = new Dictionary<GID, RowID>();
            this._FrameSelected = new Dictionary<GID, RowID>();
            this._FramedRowToReDraw = new Dictionary<int, int>();
        }
        /// <summary>
        /// Seznam elementů, které jsou vybrány permanentně (bez ohledu na přidávání výběru framováním).
        /// Tj. jde o elementy, které byly zarámovány dříve, anebo jsou postupně naklikány (s klávesou Ctrl).
        /// Key = GID grafického elementu. Value = NodeId řádku, kde je element umístěn
        /// </summary>
        private Dictionary<GID, RowID> _PermanentSelected;
        /// <summary>
        /// Seznam elementů, které jsou vybrány dočasně aktuálním framováním.
        /// Jejich soupis se interaktivně mění spolu s pohybem. Do Permanent se dostávají až závěrečným zápisem.
        /// Key = GID grafického elementu.
        /// Value = RowID = {GID + NodeID} řádku, kde je element umístěn. 
        /// Pro jeden GID řádku může existovat více vizuálních řádků, které mají unikátní NodeID.
        /// </summary>
        private Dictionary<GID, RowID> _FrameSelected;
        /// <summary>
        /// Odkaz na metodu, která umožní najít data elementu na základě jeho pointeru
        /// </summary>
        internal Func<DataPointerStr, GridDataElementCls> FindElementMethod { get; set; }
        #endregion
        #region PODPORA PRO SELECTOVÁNÍ
        /// <summary>
        /// Převede modifikační klávesy (Ctrl, Shift) na režim kombinace výběrů.
        /// Bez zadání druhého parametru (framing) vrací režim pro přidávání / odebírání jednotlivé, klikáním myší. Jako by se zadalo framing = false.
        /// </summary>
        /// <param name="modifierKeys">Klávesy, které určují režim kombinace. Typicky Shift odpovídá Add, Ctrl odpovídá Subtract a Ctrl+Shift odpovídá ExclusiveOr.</param>
        internal static GridGraphSelectCombineType ConvertModifierKeysToCombineType(Keys modifierKeys)
        {
            return ConvertModifierKeysToCombineType(modifierKeys, false);
        }
        /// <summary>
        /// Převede modifikační klávesy (Ctrl, Shift) na režim kombinace výběrů.
        /// Bez zadání druhého parametru (framing) vrací režim pro přidávání / odebírání jednotlivé, klikáním myší. Jako by se zadalo framing = false.
        /// </summary>
        /// <param name="modifierKeys">Klávesy, které určují režim kombinace. Typicky Shift odpovídá Add, Ctrl odpovídá Subtract a Ctrl+Shift odpovídá ExclusiveOr.</param>
        /// <param name="framing">Příležitost: false = označování klikáním myši na elementy / true = rámování (Framing). Má odlišné chování.</param>
        internal static GridGraphSelectCombineType ConvertModifierKeysToCombineType(Keys modifierKeys, bool framing)
        {
            // Konverze kláves na modifikátor
            GridGraphSelectCombineType combineType = GridGraphSelectCombineType.Add;
            bool ctrl = ((modifierKeys & Keys.Control) == Keys.Control);
            bool shift = ((modifierKeys & Keys.Shift) == Keys.Shift);
            if (!framing)
            {	// Pokud se prvky označují klikáním myši, pak klávesy značí:
                if (!ctrl && !shift)       // Bez ničeho: Solitaire
                    combineType = GridGraphSelectCombineType.AddSolitaire;
                else if (!ctrl && shift)   // Shift: přidává
                    combineType = GridGraphSelectCombineType.Add;
                else if (ctrl && !shift)   // Ctrl: mění stav (XOR)
                    combineType = GridGraphSelectCombineType.ExclusiveOr;
                else if (ctrl && shift)    // Ctrl+Shift: mění stav (XOR)
                    combineType = GridGraphSelectCombineType.ExclusiveOr;
            }
            else
            {	// Pokud se prvky označují framováním, pak klávesy značí:
                if (!ctrl && !shift)       // Bez ničeho: Solitaire
                    combineType = GridGraphSelectCombineType.AddSolitaire;
                else if (!ctrl && shift)   // Shift: přidává
                    combineType = GridGraphSelectCombineType.Add;
                else if (ctrl && !shift)   // Ctrl: mění stav (XOR)
                    combineType = GridGraphSelectCombineType.ExclusiveOr;
                else if (ctrl && shift)    // Ctrl+Shift: mění stav (XOR)
                    combineType = GridGraphSelectCombineType.ExclusiveOr;
            }

            return combineType;
        }
        /// <summary>
        /// Vyprázdní paměť pro Permanent prvky
        /// </summary>
        internal void PermanentClear()
        {
            this._PermanentSelected = new Dictionary<GID, RowID>();
        }
        /// <summary>
        /// Vrátí počet Permanent selectovaných objekt. Vhodné pro testy, zda je něco vybráno.
        /// </summary>
        /// <returns>Počet PermanentSelected položek.</returns>
        internal int PermanentCount { get { return (this._PermanentSelected.Count); } }
        /// <summary>
        /// Zjistí, zda daný GID je již obsažen v poli Permanent. Vrací true = je tam / false = není tam.
        /// </summary>
        /// <param name="elementGId">GID testovaného grafického elementu</param>
        /// <returns>true = je tam / false = není tam</returns>
        internal bool PermanentContain(GID elementGId)
        {
            return (this._PermanentSelected.ContainsKey(elementGId));
        }
        /// <summary>
        /// Metoda vrátí souhrn aktuálně vybraných elementů, včetně informace o jejich řádku.
        /// </summary>
        /// <returns></returns>
        internal List<DataPointerStr> PermanentGet()
        {
            List<DataPointerStr> result = new List<DataPointerStr>();
            foreach (KeyValuePair<GID, RowID> item in this._PermanentSelected)               // Všechny položky (Key = GID elementu, Value = RowID řádku):
                result.Add(new DataPointerStr(item.Value.GId, item.Key, item.Value.NodeId)); // Do výstupu: nejprve GID řádku (Value), pak GID elementu (Key), pak NodeId.
            return result;
        }
		/// <summary>
        /// Do paměti pro PermanentSelected prvky přidá / odebere daný element, podle aktuálního stavu a režimu Combined
        /// </summary>
        /// <param name="elementGId">GID grafického elementu. GID elementu je jednoznačné i přes různé GID řádků.</param>
        /// <param name="rowId">RowID jeho parenta = řádek, fixní hodnota (výběr musí přežít i rozbalení/zabalení nodu, což s sebou nese přečíslování indexů řádků)</param>
        /// <param name="combineType">Režim kombinace</param>
		internal void PermanentCombine(GID elementGId, RowID rowId, GridGraphSelectCombineType combineType)
		{
			this._PermanentCombine(elementGId, rowId, combineType);
			this.ShowSelectedInfo();
		}
        /// <summary>
        /// Do paměti pro PermanentSelected prvky přidá / odebere daný element, podle aktuálního stavu a režimu Combined
        /// </summary>
        /// <param name="elementGId">GID grafického elementu. GID elementu je jednoznačné i přes různé GID řádků.</param>
        /// <param name="rowId">RowID jeho parenta = řádek, fixní hodnota (výběr musí přežít i rozbalení/zabalení nodu, což s sebou nese přečíslování indexů řádků)</param>
        /// <param name="combineType">Režim kombinace</param>
        private void _PermanentCombine(GID elementGId, RowID rowId, GridGraphSelectCombineType combineType)
        {
            bool exist = this._PermanentSelected.ContainsKey(elementGId);
            switch (combineType)
            {
                case GridGraphSelectCombineType.AddSolitaire:
                    if (this._PermanentSelected.Count > 0)
                        this._PermanentSelected.Clear();
                    this._PermanentSelected.Add(elementGId, rowId);
                    break;
                case GridGraphSelectCombineType.Add:
                    if (!exist)
                        this._PermanentSelected.Add(elementGId, rowId);
                    break;
                case GridGraphSelectCombineType.Subtract:
                    if (exist)
                        this._PermanentSelected.Remove(elementGId);
                    break;
                case GridGraphSelectCombineType.ExclusiveOr:
                    if (exist)
                        this._PermanentSelected.Remove(elementGId);
                    else
                        this._PermanentSelected.Add(elementGId, rowId);
                    break;
            }
        }
        /// <summary>
        /// Do paměti pro PermanentSelected prvky přidá další element (daný jeho GIDem, plus GID řádku pro snadné dohledávání).
        /// </summary>
        /// <param name="elementGId">GID grafického elementu</param>
        /// <param name="rowId">RowID jeho parenta = řádek, fixní hodnota (výběr musí přežít i rozbalení/zabalení nodu, což s sebou nese přečíslování indexů řádků)</param>
        internal void PermanentAdd(GID elementGId, RowID rowId)
        {
			this._PermanentAdd(elementGId, rowId);
			this.ShowSelectedInfo();
		}
		/// <summary>
        /// Do paměti pro PermanentSelected prvky přidá další elementy, obsažené v parametru elementPairList.
        /// Toto pole je typicky vytvářeno metodou this.PermanentGet().
        /// Pamět PermanentSelect se před přidáním pole nemaže. 
        /// Pokud to volající metoda potřebuje, může si jej předem vymazat sama - metodou PermanentClear().
        /// Pokud je třeba data na vstupu kombinovat s aktuálním stavem, je možno zadat parametr combineType.
        /// </summary>
        /// <param name="elementPairList"></param>
        internal void PermanentAddList(List<DataPointerStr> elementPairList)
        {
            foreach (DataPointerStr elementPair in elementPairList)
                this._PermanentAdd(elementPair.Element, new RowID(elementPair.Row, elementPair.NodeId));
			this.ShowSelectedInfo();
		}
        /// <summary>
        /// Do paměti pro PermanentSelected prvky přidá další elementy, obsažené v parametru elementPairList.
        /// Toto pole je typicky vytvářeno metodou this.PermanentGet().
        /// Pamět PermanentSelect se před přidáním pole nemaže. 
        /// Pokud to volající metoda potřebuje, může si jej předem vymazat sama - metodou PermanentClear().
        /// Pokud je třeba data na vstupu kombinovat s aktuálním stavem, je možno zadat parametr combineType.
        /// </summary>
        /// <param name="elementPairList"></param>
        internal void PermanentAddList(List<DataPointerStr> elementPairList, GridGraphSelectCombineType combineType)
        {
            GridGraphSelectCombineType combine = combineType;
            if (combineType == GridGraphSelectCombineType.AddSolitaire)
            {
                this.PermanentClear();
                combine = GridGraphSelectCombineType.Add;
            }

            foreach (DataPointerStr elementPair in elementPairList)
                this._PermanentCombine(elementPair.Element, new RowID(elementPair.Row, elementPair.NodeId), combine);

			this.ShowSelectedInfo();
		}
        /// <summary>
        /// Vyčistí paměť pro PermanentSelected prvky (pokud není prázdná) 
        /// a pak do ní přidá daný prvek. Bude tam jen on jediný.
        /// </summary>
        /// <param name="elementGId">GID grafického elementu</param>
        /// <param name="rowId">RowId jeho parenta = řádek, fixní hodnota (výběr musí přežít i rozbalení/zabalení nodu, což s sebou nese přečíslování indexů řádků)</param>
        internal void PermanentAddSolitary(GID elementGId, RowID rowId)
        {
            if (this._PermanentSelected.Count > 0)
                this._PermanentSelected.Clear();
            this._PermanentSelected.Add(elementGId, rowId);
			this.ShowSelectedInfo();
		}
		/// <summary>
		/// Do paměti pro PermanentSelected prvky přidá další element (daný jeho GIDem, plus GID řádku pro snadné dohledávání).
		/// </summary>
		/// <param name="elementGId">GID grafického elementu</param>
		/// <param name="rowId">RowID jeho parenta = řádek, fixní hodnota (výběr musí přežít i rozbalení/zabalení nodu, což s sebou nese přečíslování indexů řádků)</param>
		private void _PermanentAdd(GID elementGId, RowID rowId)
		{
			if (this._PermanentSelected.ContainsKey(elementGId)) return;
			this._PermanentSelected.Add(elementGId, rowId);
		}
		/// <summary>
        /// Z paměti pro PermanentSelected prvky odebere určitý element (daný jeho GIDem).
        /// </summary>
        /// <param name="elementGId">GID grafického elementu</param>
        internal void PermanentRemove(GID elementGId)
        {
            if (this._PermanentSelected.ContainsKey(elementGId))
                this._PermanentSelected.Remove(elementGId);
			this.ShowSelectedInfo();
		}
        /// <summary>
        /// Vyprázdní paměť pro FrameSelected prvky
        /// </summary>
        internal void FrameClear()
        {
            this._FrameSelected = new Dictionary<GID, RowID>();
			this.ShowSelectedInfo();
		}
        /// <summary>
        /// Do paměti pro FrameSelected prvky přidá další elementy (dané jejich GIDem, plus GID řádku pro snadné dohledávání).
		/// Aktualizuje StatusBar.
		/// </summary>
        /// <param name="selectedElements"></param>
        internal void FrameAddRange(List<GridDataElementCls> selectedElements, RowID rowId)
        {
            if (selectedElements == null) return;
            foreach (GridDataElementCls selectedElement in selectedElements)
                this._FrameAdd(selectedElement.GId, rowId);
        }
        /// <summary>
        /// Do paměti pro FrameSelected prvky přidá další element (daný jeho GIDem, plus RowId řádku pro snadné dohledávání).
		/// Aktualizuje StatusBar.
        /// </summary>
        /// <param name="elementGId">GID grafického elementu</param>
        /// <param name="rowId">RowID jeho parenta = řádek, fixní hodnota (výběr musí přežít i rozbalení/zabalení nodu, což s sebou nese přečíslování indexů řádků)</param>
        internal void FrameAdd(GID elementGId, RowID rowId)
        {
			this._FrameAdd(elementGId, rowId);
			this.ShowSelectedInfo();
		}
		/// <summary>
		/// Do paměti pro FrameSelected prvky přidá další element (daný jeho GIDem, plus RowId řádku pro snadné dohledávání).
		/// Neprovádí aktualizaci StatusBaru.
		/// </summary>
		/// <param name="elementGId">GID grafického elementu</param>
		/// <param name="rowId">RowID jeho parenta = řádek, fixní hodnota (výběr musí přežít i rozbalení/zabalení nodu, což s sebou nese přečíslování indexů řádků)</param>
		private void _FrameAdd(GID elementGId, RowID rowId)
		{
			if (this._FrameSelected.ContainsKey(elementGId)) return;
			this._FrameSelected.Add(elementGId, rowId);
		}
        /// <summary>
        /// Obsah paměti pro FrameSelected prvky převede do paměti PermanentSelected prvků.
        /// Provádí se typicky po dokončení výběru pomocí FrameSelection.
        /// Bez upřesnění režimu se použije režim Add.
        /// </summary>
        internal void FrameSaveToPermanent()
        {
            this.FrameSaveToPermanent(GridGraphSelectCombineType.Add);
        }
        /// <summary>
        /// Obsah paměti pro FrameSelected prvky převede do paměti PermanentSelected prvků.
        /// Provádí se typicky po dokončení výběru pomocí FrameSelection.
        /// Bez upřesnění režimu se použije režim Add.
        /// </summary>
        /// <param name="modifierKeys">Klávesy, které určují režim kombinace. Typicky Shift odpovídá Add, Ctrl odpovídá Subtract a Ctrl+Shift odpovídá ExclusiveOr.</param>
        internal void FrameSaveToPermanent(Keys modifierKeys)
        {
            GridGraphSelectCombineType combineType = ConvertModifierKeysToCombineType(modifierKeys, true);    // Konverze kláves na modifikátor, pro účely FrameSelect
            this.FrameSaveToPermanent(combineType);
        }
        /// <summary>
        /// Obsah paměti pro FrameSelected prvky převede do paměti PermanentSelected prvků.
        /// Provádí se typicky po dokončení výběru pomocí FrameSelection.
        /// Bez upřesnění režimu se použije režim Add.
        /// </summary>
        /// <param name="combineType">Režim kombinace. Je odvozen od stisknuté klávesy. Typicky Shift odpovídá Add, Ctrl odpovídá Subtract a Ctrl+Shift odpovídá ExclusiveOr.</param>
        internal void FrameSaveToPermanent(GridGraphSelectCombineType combineType)
        {
            if (combineType == GridGraphSelectCombineType.AddSolitaire)
            {	// Pokud je na vstupu režim Solitaire, pak to řeším jinak:
                this.PermanentClear();                          // Nejprve vše z Permanent smažu,
                combineType = GridGraphSelectCombineType.Add;   //  a pak budu přidávat jako Add. Pokud bych nechal AddSolitaire, pak bych mazal-přidal-smazal-přidal...
            }

            foreach (KeyValuePair<GID, RowID> kvp in this._FrameSelected)
                this._PermanentCombine(kvp.Key, kvp.Value, combineType);

            this._FrameSelected.Clear();

			this.ShowSelectedInfo();
        }
        /// <summary>
        /// Zajistí smazání všech dat ze Selectoru, typicky po reloadu dat do grafu.
        /// </summary>
        internal void Clear()
        {
            this.PermanentClear();
            this.FrameClear();
        }
        #endregion
        #region INFORMACE DO STATUS BARU
		/// <summary>
		/// Do status baru vepíše počet vybraných elementů, a pokud není nic vybraného tak vepíše prázdný text.
		/// </summary>
		internal void ShowSelectedInfo()
		{
            string info = this.GetSelectedInfo();
            if (info == null)
				Steward.StatbusBarSet(Forms.StatusBarItem.MainText, "");
			else
                Steward.StatbusBarSet(Forms.StatusBarItem.MainText, "Selected: " + info);
		}
        /// <summary>
        /// Vrátí text do status baru
        /// </summary>
        /// <returns></returns>
        internal string GetSelectedInfo()
        {
            Dictionary<DataPointerStr, GridDataElementCls> currentSelected = this._GetSelected(true, true);
            
            // Statistika:
            int count = 0;
            double seconds = 0D;
            TimeRange totalTime = TimeRange.Empty;
            foreach (GridDataElementCls element in currentSelected.Values)
            {
                TimeRange t = element.TimeRange;
                count++;
                if (!t.IsEmpty)
                {
                    seconds += t.Seconds;
                    totalTime = totalTime + t;
                }
            }
            if (count == 0) return null;

            double average = seconds / (double)count;
            string info = count.ToString() + "  element(s); time = " + totalTime.StringFullDetail + ";     Suma = " + seconds.ToString("###,##0.000").Trim() + " sec;     Average = " + average.ToString("###,##0.000").Trim() + " sec.";
            return info;
        }
        /// <summary>
        /// Vrátí seznam aktuálně vybraných elementů
        /// </summary>
        /// <param name="addFrame"></param>
        /// <param name="addPermanent"></param>
        /// <returns></returns>
        private Dictionary<DataPointerStr, GridDataElementCls> _GetSelected(bool addFrame, bool addPermanent)
        {
            Dictionary<DataPointerStr, GridDataElementCls> result = new Dictionary<DataPointerStr, GridDataElementCls>();
            if (addFrame)
                this._AddSelected(this._FrameSelected, result);
            if (addPermanent)
                this._AddSelected(this._PermanentSelected, result);
            return result;
        }
        /// <summary>
        /// Přidá do seznamu soupis ukazatelů na elementy
        /// </summary>
        /// <param name="dictionary"></param>
        /// <param name="result"></param>
        private void _AddSelected(Dictionary<GID, RowID> dictionary, Dictionary<GID, RowID> result)
        {
            foreach (KeyValuePair<GID, RowID> item in dictionary)
            {
                if (!result.ContainsKey(item.Key))
                    result.Add(item.Key, item.Value);
            }
        }
        /// <summary>
        /// Přidá do seznamu soupis dat elementů
        /// </summary>
        /// <param name="dictionary"></param>
        /// <param name="result"></param>
        private void _AddSelected(Dictionary<GID, RowID> dictionary, Dictionary<DataPointerStr, GridDataElementCls> result)
        {
            Func<DataPointerStr, GridDataElementCls> findMethod = this.FindElementMethod;
            if (findMethod == null)
                throw new InvalidOperationException("Nelze použít metodu GridGraphSelectCls._AddSelected pro result obsahující data elementu, když není naplněn odkaz na metodu FindElementMethod!");

            foreach (KeyValuePair<GID, RowID> item in dictionary)
            {
                DataPointerStr pointer = new DataPointerStr(item.Value.GId, item.Key, item.Value.NodeId);      // Pointer na element (GID řádku, GID elementu, ID nodu)
                if (!result.ContainsKey(pointer))
                {
                    result.Add(pointer, findMethod(pointer));
                }
            }
        }
        #endregion
        #region PODPORA PRO VYKRESLOVÁNÍ
        /// <summary>
        /// Vrátí true, pokud grafický element s daným GIDem je v některém seznamu vybraných elementů (Permanent / Framed).
        /// </summary>
        /// <param name="elementGId">GID grafického elementu</param>
        /// <returns>true = element je Selected / false = není</returns>
        internal bool IsGidSelected(GID elementGId)
        {
            if (this._PermanentSelected.ContainsKey(elementGId)) return true;
            if (this._FrameSelected.ContainsKey(elementGId)) return true;
            return false;
        }
        /// <summary>
        /// Obsahuje počet grafických elementů, které jsou v kolekci PermanentSelected
        /// </summary>
        internal int PermanentSelectedCount { get { return this._PermanentSelected.Count; } }
        /// <summary>
        /// Vrátí true, pokud grafický element s daným GIDem je v některém seznamu vybraných elementů (Permanent / Framed).
        /// </summary>
        /// <param name="rowIndex">Index řádku. Framování pracuje s indexy řádků, protože Frame nepřežije žádnou větší změnu (po zvednutí myši zmizí).</param>
        /// <returns>true = Row je Framed / false = není</returns>
        internal bool IsRowIndexFramed(int rowIndex)
        {
            return (this._FramedRowToReDraw.ContainsKey(rowIndex));
        }
        /// <summary>
        /// Seznam indexů řádků, jejichž některé elementy jsou v kolekci PermanentSelected.
        /// Tento seznam je před použitím nutno aktualizovat voláním metody GridGraphCls._SelectPreparePermanentRowIndex().
        /// Důvod je prostý: výběr elementů PermanentSelected je trvanlivějšího charakteru, 
        /// při vybraných prvcích je možno rozbalovat/sbalovat nody seznamu = tím se mění indexy řádků, ale vybrané prvky zůstávají.
        /// Prvky jsou fixovány na GID elementu a GID řádku. Číslo řádku se neukládá, protože se může měnit.
        /// </summary>
        internal List<int> PermanentSelectedRowList { get { return new List<int>(this._PermanentSelectedRowList); } }
        /// <summary>
        /// Obsahuje kolekci řádků, které jsou ovlivněny framováním a je třeba je překreslit (DrawFramedRows()).
        /// </summary>
        internal List<int> FrameRowToDraw { get { return new List<int>(this._FramedRowToReDraw.Keys); } }
        #endregion
        #region PODPORA PRO URČOVÁNÍ ŘÁDKŮ, KTERÉ JSOU SELECTOVÁNY, A MAJÍ SE PŘEKRESLOVAT
        /// <summary>
        /// Připraví seznam řádků, jejichž některý element je ve stavu PermanentSelected.
        /// K přípravě potřebuje delegáta = metodu, která vrátí index řádku pro určitý GID.
        /// </summary>
        /// <param name="getRowIndexMethod">Metoda, která </param>
        internal void PreparePermanentRowIndex(GetRowIndexFromRowIdDelegate getRowIndexMethod)
        {
            // 1. Připravím unikátní seznam NodeId řádků:
            Dictionary<int, int> nodeIdDict = new Dictionary<int, int>();   // Key = NodeId, value = RowIndex
            foreach (RowID rowId in this._PermanentSelected.Values)
            {
                if (nodeIdDict.ContainsKey(rowId.NodeId)) continue;
                nodeIdDict.Add(rowId.NodeId, getRowIndexMethod(rowId));     // Pro určení indexu řádku použijeme delegovanou metodu
            }

            // 2. Vytvořím a naplním a setřídím seznam _PermanentSelectedRowList:
            this._PermanentSelectedRowList = new List<int>(nodeIdDict.Values);
            this._PermanentSelectedRowList.Sort();
        }
        /// <summary>
        /// Úložiště seznamu řádků, které obsahují PermanentSelected elementy
        /// </summary>
        private List<int> _PermanentSelectedRowList;
        /// <summary>
        /// Předpis delegáta pro metodu, která vrátí index řádku, na kterém se nachází určitý RowID.
        /// </summary>
        /// <param name="rowGId">GID řádku</param>
        /// <returns>Index řádku</returns>
        internal delegate int GetRowIndexFromRowIdDelegate(RowID rowId);
        #endregion
        #region PODPORA PRO URČOVÁNÍ ŘÁDKŮ, KTERÉ JSOU FRAMOVÁNY, A MAJÍ SE PŘEKRESLOVAT
        /// <summary>
        /// Zahájení framování: nulovat pole řádků, které jsou dotčeny framováním.
        /// </summary>
        internal void FrameRowClear()
        {
            this._FramedRowToReDraw = new Dictionary<int, int>();
        }
        /// <summary>
        /// Průběh framování: do pole řádků, které jsou dotčeny framováním přidat další řádky (pokud tam dosud nejsou).
        /// Význam:
        /// Toto pole si registruje všechny řádky, které byly před chvilkou rámovány, a které jsou rámovány nyní.
        /// V průběhu rámování se rozsah framu interaktivně mění, tj. řádky přibývají i ubývají.
        /// Při vykreslování nelze vykreslovat jen řádky, které jsou nyní aktivně framovány:
        /// tím bychom nevykreslili ty řádky, které byly framovány před chvilkou, nyní z nich frame odešel a je zapotřebí je také překreslit 
        /// (nyní už bez framu).
        /// Takže si pamatujeme řádky s minulým framem, přidáme k nim nový frame, tento souhrn překreslíme, 
        /// a pak po překreslení z pole odstraníme ty řádky, které nejsou v současném framu (metodou this.FrameRowCropRange(...).
        /// </summary>
        /// <param name="rowIndexFirst">Index prvního řádku, který je framován (včetně)</param>
        /// <param name="rowIndexLast">Index posledního řádku, který je framován (včetně), je rovný nebo vyšší než First.</param>
        internal void FrameRowAddRange(int rowIndexFirst, int rowIndexLast)
        {
            // Zajistíme, aby v this._FramedRowToReDraw byly všechny hodnoty v rozsahu (first ÷ last) včetně:
            for (int i = rowIndexFirst; i <= rowIndexLast; i++)
            {
                if (!this._FramedRowToReDraw.ContainsKey(i))
                    this._FramedRowToReDraw.Add(i, 1);
            }
        }
        /// <summary>
        /// Průběh framování: z pole řádků, které jsou dotčeny framováním odebere ty řádky, které jsou mimo zadaný rozsah.
        /// Detaily jsou uvedeny v popisu metody this.FrameRowAddRange(...).
        /// </summary>
        /// <param name="rowIndexFirst">Index prvního řádku, který je framován (včetně)</param>
        /// <param name="rowIndexLast">Index posledního řádku, který je framován (včetně), je rovný nebo vyšší než First.</param>
        internal void FrameRowCropRange(int rowIndexFirst, int rowIndexLast)
        {
            // Protože kolekcí Dictionary nemohu procházet, a současně z ní mazat data (foreach () { if (outside)  Remove() } => error),
            // tak si nejprve připravím index klíčů ke smazání, a teprve pak je smažu:
            List<int> removeList = new List<int>();
            foreach (int rowIndex in this._FramedRowToReDraw.Keys)
            {
                if (rowIndex < rowIndexFirst || rowIndex > rowIndexLast)
                    removeList.Add(rowIndex);
            }

            // A nyní smazat ty, co se smazat mají:
            foreach (int rowIndex in removeList)
                this._FramedRowToReDraw.Remove(rowIndex);
        }
        /// <summary>
        /// Pole řádků, které jsou dotčeny framováním.
        /// Při překreslování controlu v průběhu framování stačí překreslit tyto řádky.
        /// Pole se předává do kreslící metody přes property this.FramedRows.
        /// </summary>
        private Dictionary<int, int> _FramedRowToReDraw;
        #endregion
    }
    #endregion
    #region ENUM GridGraphSelectCombineType : REŽIMY KOMBINACE VÝBĚRU (SELECTION ADD/SUB/XOR)
    /// <summary>
    /// Režimy kombinací pole PermanentSelected a FrameSelected.
    /// Jde o režim kombinace nově vybíraných prvků (Framováním) s prvky, které jsou vybrány trvale (Permanent).
    /// Umožňuje nejprve vybrat velkou oblast, a z ní například odmazávat následným malým výběrem.
    /// </summary>
    internal enum GridGraphSelectCombineType
    {
        /// <summary>Přidá zadaný element ke sbírce dosavadních vybraných elementů. Pokud tam element již existuje, nepřidává ani neodebírá jej. Sjednocení množin.</summary>
        Add = 1,
        /// <summary>Nejprve vyprázdní sbírku, a pak přidá zadaný element jako jediný.</summary>
        AddSolitaire,
        /// <summary>Odebere zadaný element ze sbírky dosavadních vybraných elementů. Pokud tam element neexistuje, nedělá nic. Odečtení prvku z množiny.</summary>
        Subtract,
        /// <summary>Přidá nebo odebere zadaný element ze sbírky dosavadních vybraných elementů. Změní stav. Pokud tam je, odebere jej. Pokud tam není, přidá jej.</summary>
        ExclusiveOr
    }
    #endregion
}
